<?php
namespace TYPO3\Flow\Cache\Backend;
/*                                                                        *
 * This script belongs to the TYPO3 Flow framework.                       *
 *                                                                        *
 * It is free software; you can redistribute it and/or modify it under    *
 * the terms of the GNU Lesser General Public License, either version 3   *
 * of the License, or (at your option) any later version.                 *
 *                                                                        *
 * The TYPO3 project - inspiring people to share!                         *
 *                                                                        */


/**
 * A caching backend which stores cache entries by using Redis with phpredis
 * PHP module. Redis is a noSQL database with very good scaling characteristics
 * in proportion to the amount of entries and data size.
 *
 * @see http://code.google.com/p/redis/
 * @see http://github.com/owlient/phpredis
 *
 * Warning:
 * Redis and phpredis are young projects with very high development speed.
 * This implementation should be considered as experimental for now,
 * internals might break or change while the dependent projects mature.
 *
 * Successfully tested with:
 * - redis
 *   version 2.0.0-rc2, version 1.2.0 does not work
 *   git version 9fd01051bf8400babcca73a76a67dfc1847633ff from 2010-11-12
 * - phpredis
 *   git version 0abb9e5ec07b8a8c20b5 from 2010-07-18
 *   git version 12769b03c8ec17b25573e0453003712011bba241 from 2010-11-08
 *
 * Implementation based on ext:rediscache by Christopher Hlubek - networkteam GmbH
 *
 * This backend uses the following types of redis keys:
 * - identData:xxx, value type "string", volatile, expires after given lifetime
 *   xxx is the given identifier name, value is the cache data
 * - identTags:xxx, value type "set"
 *   xxx is the given identifier name, value is a set of associated tags.
 *   This is a "reverse" tag index. It provides quick access for all tags
 *   associated with this identifier and is used when removing the identifier.
 * - tagIdents:xxx, value type "set"
 *   xxx is a tag name, value is a set of associated identifiers.
 *   This is "forward" tag index. It is mainly used for flushing content by tag.
 * - temp:xxx, value type "set"
 *   xxx is a unique id, value is a set of identifiers. Used as temporary key
 *   used in flushByTag() and flushByTags(), removed after usage again.
 *
 * Each cache using this backend should use an own redis database to
 * avoid namespace problems. By default redis has 16 databases which are
 * identified with numbers 0 .. 15. setDatabase() can be used to select one.
 * The unit tests use and flush database numbers 0 and 1, production use should start from 2.
 *
 * @api
 */
class RedisBackend_Original extends AbstractBackend implements TaggableBackendInterface {

	/**
	 * Faked unlimited lifetime = 31536000 (1 Year).
	 * In redis an entry does not have a lifetime by default (it's not "volatile").
	 * Entries can be made volatile either with EXPIRE after it has been SET,
	 * or with SETEX, which is a combined SET and EXPIRE command.
	 * But an entry can not be made "unvolatile" again. To set a volatile entry to
	 * not volatile again, it must be DELeted and SET without a following EXPIRE.
	 * To save these additional calls on every set(),
	 * we just make every entry volatile and treat a high number as "unlimited"
	 *
	 * @see http://code.google.com/p/redis/wiki/ExpireCommand
	 * @var integer Faked unlimited lifetime
	 */
	const FAKED_UNLIMITED_LIFETIME = 31536000;

	/**
	 * Key prefix for identifier->data entries
	 * @var string
	 */
	const IDENTIFIER_DATA_PREFIX = 'identData:';

	/**
	 * Key prefix for identifier->tags sets
	 * @var string
	 */
	const IDENTIFIER_TAGS_PREFIX = 'identTags:';

	/**
	 * Key prefix for tag->identifiers sets
	 * @var string
	 */
	const TAG_IDENTIFIERS_PREFIX = 'tagIdents:';

	/**
	 * Instance of the PHP redis class
	 * @var \Redis
	 */
	protected $redis;

	/**
	 * Indicates whether the server is connected
	 * @var boolean
	 */
	protected $connected = FALSE;

	/**
	 * Hostname / IP of the Redis server, defaults to 127.0.0.1.
	 * @var string
	 */
	protected $hostname = '127.0.0.1';

	/**
	 * Port of the Redis server, defaults to 6379
	 * @var integer
	 */
	protected $port = 6379;

	/**
	 * Number of selected database, defaults to 0
	 * @var integer
	 */
	protected $database = 0;

	/**
	 * Password for redis authentication
	 * @var string
	 */
	protected $password = '';

	/**
	 * Indicates whether data is compressed or not (requires php zlib)
	 * @var boolean
	 */
	protected $compression = FALSE;

	/**
	 * -1 to 9, indicates zlib compression level: -1 = default level 6, 0 = no compression, 9 maximum compression
	 * @var integer
	 */
	protected $compressionLevel = -1;

	/**
	 * Construct this backend
	 *
	 * @param \TYPO3\Flow\Core\ApplicationContext $context Flow's application context
	 * @param array $options Configuration options
	 * @throws \TYPO3\Flow\Cache\Exception if php redis module is not loaded
	 */
	public function __construct(\TYPO3\Flow\Core\ApplicationContext $context, array $options = array()) {
		if (!extension_loaded('redis')) {
			throw new \TYPO3\Flow\Cache\Exception('The PHP extension "redis" must be installed and loaded in order to use the phpredis redis backend.', 1279462933);
		}

		parent::__construct($context, $options);
	}

	/**
	 * Initializes the redis backend
	 *
	 * @throws \TYPO3\Flow\Cache\Exception if access to redis with password is denied or if database selection fails
	 * @return void
	 */
	public function initializeObject() {
		$this->redis = new \Redis();

		try {
			$this->connected = $this->redis->connect($this->hostname, $this->port);
		} catch (\Exception $e) {
			throw new \TYPO3\Flow\Cache\Exception('Could not connect to redis server.', 1294734537, $e);
		}

		if ($this->connected) {
			if (strlen($this->password)) {
				$success = $this->redis->auth($this->password);
				if (!$success) {
					throw new \TYPO3\Flow\Cache\Exception('The given password was not accepted by the redis server.', 1279765134);
				}
			}

			if ($this->database > 0) {
				$success = $this->redis->select($this->database);
				if (!$success) {
					throw new \TYPO3\Flow\Cache\Exception('The given database "' . $this->database . '" could not be selected.', 1279765144);
				}
			}
		}
	}

	/**
	 * Setter for server hostname
	 *
	 * @param string $hostname Hostname
	 * @return void
	 */
	public function setHostname($hostname) {
		$this->hostname = $hostname;
	}

	/**
	 * Setter for server port
	 *
	 * @param integer $port Port
	 * @return void
	 * @api
	 */
	public function setPort($port) {
		$this->port = $port;
	}

	/**
	 * Setter for database number
	 *
	 * @param integer $database Database
	 * @return void
	 * @throws \InvalidArgumentException if database number is not valid
	 * @api
	 */
	public function setDatabase($database) {
		if (!is_integer($database)) {
			throw new \InvalidArgumentException('The specified database number is of type "' . gettype($database) . '" but an integer is expected.', 1279763057);
		}
		if ($database < 0) {
			throw new \InvalidArgumentException('The specified database "' . $database . '" must be greater or equal than zero.', 1279763534);
		}

		$this->database = $database;
	}

	/**
	 * Setter for authentication password
	 *
	 * @param string $password Password
	 * @return void
	 * @api
	 */
	public function setPassword($password) {
		$this->password = $password;
	}

	/**
	 * Enable data compression
	 *
	 * @param boolean $compression TRUE to enable compression
	 * @return void
	 * @throws \InvalidArgumentException
	 * @api
	 */
	public function setCompression($compression) {
		if (!is_bool($compression)) {
			throw new \InvalidArgumentException('The specified compression of type "' . gettype($compression) . '" but an boolean is expected.', 1289679153);
		}

		$this->compression = $compression;
	}

	/**
	 * Set data compression level.
	 * If compression is enabled and this is not set,
	 * gzcompress default level will be used
	 *
	 * @param integer $compressionLevel -1 to 9: Compression level
	 * @return void
	 * @throws \InvalidArgumentException
	 */
	public function setCompressionLevel($compressionLevel) {
		if (!is_integer($compressionLevel)) {
			throw new \InvalidArgumentException('The specified compression of type "' . gettype($compressionLevel) . '" but an integer is expected.', 1289679154);
		}

		if ($compressionLevel >= -1 && $compressionLevel <= 9) {
			$this->compressionLevel = $compressionLevel;
		} else {
			throw new \InvalidArgumentException('The specified compression level must be an integer between -1 and 9.', 1289679155);
		}
	}

	/**
	 * Save data in the cache
	 *
	 * Scales O(1) with number of cache entries
	 * Scales O(n) with number of tags
	 *
	 * @param string $entryIdentifier Identifier for this specific cache entry
	 * @param string $data Data to be stored
	 * @param array $tags Tags to associate with this cache entry
	 * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, default lifetime is used. "0" means unlimited lifetime.
	 * @return void
	 * @throws \InvalidArgumentException if identifier is not valid
	 * @throws \TYPO3\Flow\Cache\Exception\InvalidDataException if data is not a string
	 * @api
	 */
	public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
		if (!is_string($entryIdentifier)) {
			throw new \InvalidArgumentException('The specified identifier is of type "' . gettype($entryIdentifier) . '" but a string is expected.', 1279470252);
		}
		if (!is_string($data)) {
			throw new \TYPO3\Flow\Cache\Exception\InvalidDataException('The specified data is of type "' . gettype($data) . '" but a string is expected.', 1279469941);
		}

		$lifetime = $lifetime === NULL ? $this->defaultLifetime : $lifetime;
		if (!is_integer($lifetime)) {
			throw new \InvalidArgumentException('The specified lifetime is of type "' . gettype($lifetime) . '" but an integer or NULL is expected.', 1279488008);
		}
		if ($lifetime < 0) {
			throw new \InvalidArgumentException('The specified lifetime "' . $lifetime . '" must be greater than or equal to zero.', 1279487573);
		}

		if ($this->connected) {
			$expiration = $lifetime === 0 ? self::FAKED_UNLIMITED_LIFETIME : $lifetime;

			if ($this->compression) {
				$data = gzcompress($data, $this->compressionLevel);
			}

			$this->redis->setex(self::IDENTIFIER_DATA_PREFIX . $entryIdentifier, $expiration, $data);

			$addTags = $tags;
			$removeTags = array();
			$existingTags = $this->redis->sMembers(self::IDENTIFIER_TAGS_PREFIX . $entryIdentifier);
			if (!empty($existingTags)) {
				$addTags = array_diff($tags, $existingTags);
				$removeTags = array_diff($existingTags, $tags);
			}

			if (count($removeTags) > 0 || count($addTags) > 0) {
				$queue = $this->redis->multi(\Redis::PIPELINE);
				foreach ($removeTags as $tag) {
					$queue->sRemove(self::IDENTIFIER_TAGS_PREFIX . $entryIdentifier, $tag);
					$queue->sRemove(self::TAG_IDENTIFIERS_PREFIX . $tag, $entryIdentifier);
				}

				foreach ($addTags as $tag) {
					$queue->sAdd(self::IDENTIFIER_TAGS_PREFIX . $entryIdentifier, $tag);
					$queue->sAdd(self::TAG_IDENTIFIERS_PREFIX . $tag, $entryIdentifier);
				}
				$queue->exec();
			}
		}
	}

	/**
	 * Loads data from the cache.
	 *
	 * Scales O(1) with number of cache entries
	 *
	 * @param string $entryIdentifier An identifier which describes the cache entry to load
	 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
	 * @throws \InvalidArgumentException if identifier is not a string
	 * @api
	 */
	public function get($entryIdentifier) {
		if (!is_string($entryIdentifier)) {
			throw new \InvalidArgumentException('The specified identifier is of type "' . gettype($entryIdentifier) . '" but a string is expected.', 1279470253);
		}

		$storedEntry = FALSE;
		if ($this->connected) {
			$storedEntry = $this->redis->get(self::IDENTIFIER_DATA_PREFIX . $entryIdentifier);
		}

		if ($this->compression && strlen($storedEntry) > 0) {
			$storedEntry = gzuncompress($storedEntry);
		}

		return $storedEntry;
	}

	/**
	 * Checks if a cache entry with the specified identifier exists.
	 *
	 * Scales O(1) with number of cache entries
	 *
	 * @param string $entryIdentifier Identifier specifying the cache entry
	 * @return boolean TRUE if such an entry exists, FALSE if not
	 * @throws \InvalidArgumentException if identifier is not a string
	 * @api
	 */
	public function has($entryIdentifier) {
		if (!is_string($entryIdentifier)) {
			throw new \InvalidArgumentException('The specified identifier is of type "' . gettype($entryIdentifier) . '" but a string is expected.', 1279470254);
		}
		return $this->connected && $this->redis->exists(self::IDENTIFIER_DATA_PREFIX . $entryIdentifier);
	}

	/**
	 * Removes all cache entries matching the specified identifier.
	 *
	 * Scales O(1) with number of cache entries
	 * Scales O(n) with number of tags
	 *
	 * @param string $entryIdentifier Specifies the cache entry to remove
	 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
	 * @throws \InvalidArgumentException if identifier is not a string
	 * @api
	 */
	public function remove($entryIdentifier) {
		if (!is_string($entryIdentifier)) {
			throw new \InvalidArgumentException('The specified identifier is of type "' . gettype($entryIdentifier) . '" but a string is expected.', 1279470255);
		}

		$elementsDeleted = FALSE;
		if ($this->connected) {
			if ($this->redis->exists(self::IDENTIFIER_DATA_PREFIX . $entryIdentifier)) {
				$assignedTags = $this->redis->sMembers(self::IDENTIFIER_TAGS_PREFIX . $entryIdentifier);

				$queue = $this->redis->multi(\Redis::PIPELINE);
				foreach ($assignedTags as $tag) {
					$queue->sRemove(self::TAG_IDENTIFIERS_PREFIX . $tag, $entryIdentifier);
				}
				$queue->delete(self::IDENTIFIER_DATA_PREFIX . $entryIdentifier, self::IDENTIFIER_TAGS_PREFIX . $entryIdentifier);
				$queue->exec();
				$elementsDeleted = TRUE;
			}
		}

		return $elementsDeleted;
	}

	/**
	 * Finds and returns all cache entry identifiers which are tagged by the
	 * specified tag.
	 *
	 * Scales O(1) with number of cache entries
	 * Scales O(n) with number of tag entries
	 *
	 * @param string $tag The tag to search for
	 * @return array An array of entries with all matching entries. An empty array if no entries matched
	 * @throws \InvalidArgumentException if tag is not a string
	 * @api
	 */
	public function findIdentifiersByTag($tag) {
		if (!is_string($tag)) {
			throw new \InvalidArgumentException('The specified tag is of type "' . gettype($tag) . '" but a string is expected.', 1279569759);
		}

		$foundIdentifiers = array();
		if ($this->connected) {
			$foundIdentifiers = $this->redis->sMembers(self::TAG_IDENTIFIERS_PREFIX . $tag);
		}

		return $foundIdentifiers;
	}

	/**
	 * Removes all cache entries of this cache.
	 *
	 * Scales O(1) with number of cache entries
	 *
	 * @return void
	 * @api
	 */
	public function flush() {
		if ($this->connected) {
			$this->redis->flushdb();
		}
	}

	/**
	 * Removes all cache entries of this cache which are tagged with the specified tag.
	 *
	 * Scales O(1) with number of cache entries
	 * Scales O(n^2) with number of tag entries
	 *
	 * @param string $tag Tag the entries must have
	 * @return integer The number of entries which have been affected by this flush
	 * @throws \InvalidArgumentException if identifier is not a string
	 * @api
	 */
	public function flushByTag($tag) {
		if (!is_string($tag)) {
			throw new \InvalidArgumentException('The specified tag is of type "' . gettype($tag) . '" but a string is expected.', 1279578078);
		}

		if ($this->connected) {
			$identifiers = $this->redis->sMembers(self::TAG_IDENTIFIERS_PREFIX . $tag);

			if (count($identifiers) > 0) {
				$this->removeIdentifierEntriesAndRelations($identifiers, array($tag));
			}
		}

		return count($identifiers);
	}

	/**
	 * With the current internal structure, only the identifier to data entries
	 * have a redis internal lifetime. If an entry expires, attached
	 * identifier to tags and tag to identifiers entries will be left over.
	 * This methods finds those entries and cleans them up.
	 *
	 * Scales O(n*m) with number of cache entries (n) and number of tags (m)
	 *
	 * @return void
	 * @api
	 */
	public function collectGarbage() {
		$identifierToTagsKeys = $this->redis->getKeys(self::IDENTIFIER_TAGS_PREFIX . '*');
		foreach ($identifierToTagsKeys as $identifierToTagsKey) {
			list(,$identifier) = explode(':', $identifierToTagsKey);
			// Check if the data entry still exists
			if (!$this->redis->exists(self::IDENTIFIER_DATA_PREFIX . $identifier)) {
				$tagsToRemoveIdentifierFrom = $this->redis->sMembers($identifierToTagsKey);
				$queue = $this->redis->multi(\Redis::PIPELINE);
				$queue->delete($identifierToTagsKey);
				foreach ($tagsToRemoveIdentifierFrom as $tag) {
					$queue->sRemove(self::TAG_IDENTIFIERS_PREFIX . $tag, $identifier);
				}
				$queue->exec();
			}
		}
	}

	/**
	 * Helper method for flushByTag() and flushByTags()
	 * Gets list of identifiers and tags and removes all relations of those tags
	 *
	 * Scales O(1) with number of cache entries
	 * Scales O(n^2) with number of tags
	 *
	 * @param array $identifiers List of identifiers to remove
	 * @param array $tags List of tags to be handled
	 * @return void
	 */
	protected function removeIdentifierEntriesAndRelations(array $identifiers, array $tags) {
		// Set an temporary entry which holds all identifiers that need to be removed from
		// the tag to identifiers sets
		$uniqueTempKey = 'temp:' . uniqId();
		$prefixedKeysToDelete = array($uniqueTempKey);

		$prefixedIdentifierToTagsKeysToDelete = array();
		foreach ($identifiers as $identifier) {
			$prefixedKeysToDelete[] = self::IDENTIFIER_DATA_PREFIX . $identifier;
			$prefixedIdentifierToTagsKeysToDelete[] = self::IDENTIFIER_TAGS_PREFIX . $identifier;
		}
		foreach ($tags as $tag) {
			$prefixedKeysToDelete[] = self::TAG_IDENTIFIERS_PREFIX . $tag;
		}

		$tagToIdentifiersSetsToRemoveIdentifiersFrom = $this->redis->sUnion($prefixedIdentifierToTagsKeysToDelete);

		// Remove the tag to identifier set of the given tags, they will be removed anyway
		$tagToIdentifiersSetsToRemoveIdentifiersFrom = array_diff($tagToIdentifiersSetsToRemoveIdentifiersFrom, $tags);

		// Diff all identifiers that must be removed from tag to identifiers sets off from a
		// tag to identifiers set and store result in same tag to identifiers set again
		$queue = $this->redis->multi(\Redis::PIPELINE);
		foreach ($identifiers as $identifier) {
			$queue->sAdd($uniqueTempKey, $identifier);
		}
		foreach ($tagToIdentifiersSetsToRemoveIdentifiersFrom as $tagToIdentifiersSet) {
			$queue->sDiffStore(
				self::TAG_IDENTIFIERS_PREFIX . $tagToIdentifiersSet,
				self::TAG_IDENTIFIERS_PREFIX . $tagToIdentifiersSet,
				$uniqueTempKey
			);
		}

		$queue->delete(array_merge($prefixedKeysToDelete, $prefixedIdentifierToTagsKeysToDelete));
		$queue->exec();
	}
}
namespace TYPO3\Flow\Cache\Backend;

use Doctrine\ORM\Mapping as ORM;
use TYPO3\Flow\Annotations as Flow;

/**
 * A caching backend which stores cache entries by using Redis with phpredis
 * PHP module. Redis is a noSQL database with very good scaling characteristics
 * in proportion to the amount of entries and data size.
 */
class RedisBackend extends RedisBackend_Original implements \TYPO3\Flow\Object\Proxy\ProxyInterface {


	/**
	 * Autogenerated Proxy Method
	 * @param \TYPO3\Flow\Core\ApplicationContext $context Flow's application context
	 * @param array $options Configuration options
	 * @throws \TYPO3\Flow\Cache\Exception if php redis module is not loaded
	 */
	public function __construct() {
		$arguments = func_get_args();

		if (!array_key_exists(0, $arguments)) $arguments[0] = \TYPO3\Flow\Core\Bootstrap::$staticObjectManager->get('TYPO3\Flow\Core\ApplicationContext');
		if (!array_key_exists(1, $arguments)) $arguments[1] = array (
);
		if (!array_key_exists(0, $arguments)) throw new \TYPO3\Flow\Object\Exception\UnresolvedDependenciesException('Missing required constructor argument $context in class ' . __CLASS__ . '. Note that constructor injection is only support for objects of scope singleton (and this is not a singleton) – for other scopes you must pass each required argument to the constructor yourself.', 1296143788);
		call_user_func_array('parent::__construct', $arguments);
		if ('TYPO3\Flow\Cache\Backend\RedisBackend' === get_class($this)) {
			$this->Flow_Proxy_injectProperties();
		}

		if (get_class($this) === 'TYPO3\Flow\Cache\Backend\RedisBackend') {
			$this->initializeObject(1);
		}
	}

	/**
	 * Autogenerated Proxy Method
	 */
	 public function __wakeup() {

	if (property_exists($this, 'Flow_Persistence_RelatedEntities') && is_array($this->Flow_Persistence_RelatedEntities)) {
		$persistenceManager = \TYPO3\Flow\Core\Bootstrap::$staticObjectManager->get('TYPO3\Flow\Persistence\PersistenceManagerInterface');
		foreach ($this->Flow_Persistence_RelatedEntities as $entityInformation) {
			$entity = $persistenceManager->getObjectByIdentifier($entityInformation['identifier'], $entityInformation['entityType'], TRUE);
			if (isset($entityInformation['entityPath'])) {
				$this->$entityInformation['propertyName'] = \TYPO3\Flow\Utility\Arrays::setValueByPath($this->$entityInformation['propertyName'], $entityInformation['entityPath'], $entity);
			} else {
				$this->$entityInformation['propertyName'] = $entity;
			}
		}
		unset($this->Flow_Persistence_RelatedEntities);
	}
				$this->Flow_Proxy_injectProperties();
		$result = NULL;

		if (get_class($this) === 'TYPO3\Flow\Cache\Backend\RedisBackend') {
			$this->initializeObject(2);
		}
		return $result;
	}

	/**
	 * Autogenerated Proxy Method
	 */
	 public function __sleep() {
		$result = NULL;
		$this->Flow_Object_PropertiesToSerialize = array();
	$reflectionService = \TYPO3\Flow\Core\Bootstrap::$staticObjectManager->get('TYPO3\Flow\Reflection\ReflectionService');
	$reflectedClass = new \ReflectionClass('TYPO3\Flow\Cache\Backend\RedisBackend');
	$allReflectedProperties = $reflectedClass->getProperties();
	foreach ($allReflectedProperties as $reflectionProperty) {
		$propertyName = $reflectionProperty->name;
		if (in_array($propertyName, array('Flow_Aop_Proxy_targetMethodsAndGroupedAdvices', 'Flow_Aop_Proxy_groupedAdviceChains', 'Flow_Aop_Proxy_methodIsInAdviceMode'))) continue;
		if (isset($this->Flow_Injected_Properties) && is_array($this->Flow_Injected_Properties) && in_array($propertyName, $this->Flow_Injected_Properties)) continue;
		if ($reflectionService->isPropertyAnnotatedWith('TYPO3\Flow\Cache\Backend\RedisBackend', $propertyName, 'TYPO3\Flow\Annotations\Transient')) continue;
		if (is_array($this->$propertyName) || (is_object($this->$propertyName) && ($this->$propertyName instanceof \ArrayObject || $this->$propertyName instanceof \SplObjectStorage ||$this->$propertyName instanceof \Doctrine\Common\Collections\Collection))) {
			foreach ($this->$propertyName as $key => $value) {
				$this->searchForEntitiesAndStoreIdentifierArray((string)$key, $value, $propertyName);
			}
		}
		if (is_object($this->$propertyName) && !$this->$propertyName instanceof \Doctrine\Common\Collections\Collection) {
			if ($this->$propertyName instanceof \Doctrine\ORM\Proxy\Proxy) {
				$className = get_parent_class($this->$propertyName);
			} else {
				$varTagValues = $reflectionService->getPropertyTagValues('TYPO3\Flow\Cache\Backend\RedisBackend', $propertyName, 'var');
				if (count($varTagValues) > 0) {
					$className = trim($varTagValues[0], '\\');
				}
				if (\TYPO3\Flow\Core\Bootstrap::$staticObjectManager->isRegistered($className) === FALSE) {
					$className = \TYPO3\Flow\Core\Bootstrap::$staticObjectManager->getObjectNameByClassName(get_class($this->$propertyName));
				}
			}
			if ($this->$propertyName instanceof \TYPO3\Flow\Persistence\Aspect\PersistenceMagicInterface && !\TYPO3\Flow\Core\Bootstrap::$staticObjectManager->get('TYPO3\Flow\Persistence\PersistenceManagerInterface')->isNewObject($this->$propertyName) || $this->$propertyName instanceof \Doctrine\ORM\Proxy\Proxy) {
				if (!property_exists($this, 'Flow_Persistence_RelatedEntities') || !is_array($this->Flow_Persistence_RelatedEntities)) {
					$this->Flow_Persistence_RelatedEntities = array();
					$this->Flow_Object_PropertiesToSerialize[] = 'Flow_Persistence_RelatedEntities';
				}
				$identifier = \TYPO3\Flow\Core\Bootstrap::$staticObjectManager->get('TYPO3\Flow\Persistence\PersistenceManagerInterface')->getIdentifierByObject($this->$propertyName);
				if (!$identifier && $this->$propertyName instanceof \Doctrine\ORM\Proxy\Proxy) {
					$identifier = current(\TYPO3\Flow\Reflection\ObjectAccess::getProperty($this->$propertyName, '_identifier', TRUE));
				}
				$this->Flow_Persistence_RelatedEntities[$propertyName] = array(
					'propertyName' => $propertyName,
					'entityType' => $className,
					'identifier' => $identifier
				);
				continue;
			}
			if ($className !== FALSE && (\TYPO3\Flow\Core\Bootstrap::$staticObjectManager->getScope($className) === \TYPO3\Flow\Object\Configuration\Configuration::SCOPE_SINGLETON || $className === 'TYPO3\Flow\Object\DependencyInjection\DependencyProxy')) {
				continue;
			}
		}
		$this->Flow_Object_PropertiesToSerialize[] = $propertyName;
	}
	$result = $this->Flow_Object_PropertiesToSerialize;
		return $result;
	}

	/**
	 * Autogenerated Proxy Method
	 */
	 private function searchForEntitiesAndStoreIdentifierArray($path, $propertyValue, $originalPropertyName) {

		if (is_array($propertyValue) || (is_object($propertyValue) && ($propertyValue instanceof \ArrayObject || $propertyValue instanceof \SplObjectStorage))) {
			foreach ($propertyValue as $key => $value) {
				$this->searchForEntitiesAndStoreIdentifierArray($path . '.' . $key, $value, $originalPropertyName);
			}
		} elseif ($propertyValue instanceof \TYPO3\Flow\Persistence\Aspect\PersistenceMagicInterface && !\TYPO3\Flow\Core\Bootstrap::$staticObjectManager->get('TYPO3\Flow\Persistence\PersistenceManagerInterface')->isNewObject($propertyValue) || $propertyValue instanceof \Doctrine\ORM\Proxy\Proxy) {
			if (!property_exists($this, 'Flow_Persistence_RelatedEntities') || !is_array($this->Flow_Persistence_RelatedEntities)) {
				$this->Flow_Persistence_RelatedEntities = array();
				$this->Flow_Object_PropertiesToSerialize[] = 'Flow_Persistence_RelatedEntities';
			}
			if ($propertyValue instanceof \Doctrine\ORM\Proxy\Proxy) {
				$className = get_parent_class($propertyValue);
			} else {
				$className = \TYPO3\Flow\Core\Bootstrap::$staticObjectManager->getObjectNameByClassName(get_class($propertyValue));
			}
			$identifier = \TYPO3\Flow\Core\Bootstrap::$staticObjectManager->get('TYPO3\Flow\Persistence\PersistenceManagerInterface')->getIdentifierByObject($propertyValue);
			if (!$identifier && $propertyValue instanceof \Doctrine\ORM\Proxy\Proxy) {
				$identifier = current(\TYPO3\Flow\Reflection\ObjectAccess::getProperty($propertyValue, '_identifier', TRUE));
			}
			$this->Flow_Persistence_RelatedEntities[$originalPropertyName . '.' . $path] = array(
				'propertyName' => $originalPropertyName,
				'entityType' => $className,
				'identifier' => $identifier,
				'entityPath' => $path
			);
			$this->$originalPropertyName = \TYPO3\Flow\Utility\Arrays::setValueByPath($this->$originalPropertyName, $path, NULL);
		}
			}

	/**
	 * Autogenerated Proxy Method
	 */
	 private function Flow_Proxy_injectProperties() {
		$this->injectEnvironment(\TYPO3\Flow\Core\Bootstrap::$staticObjectManager->get('TYPO3\Flow\Utility\Environment'));
$this->Flow_Injected_Properties = array (
  0 => 'environment',
);
	}
}
#